package bsh;

import java.math.BigDecimal;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.MethodDescriptor;
import bsh.meta.Scope;
import bsh.meta.TypeWrapper;
import bsh.utils.reflection.BytecodeHelper;

/**
 * Compilador para operacoes binarias +, -, etc
 * 
 * @author takeshi
 */
public class BinaryExpressionCompiler implements NodeCompiler {

    private static final int NUM_3 = 3;

    private final BSHBinaryExpression node;

    private final CompilationUnit unit;

    /**
     * @param node {@link BSHBinaryExpression}
     * @param unit {@link CompilationUnit}
     */
    public BinaryExpressionCompiler(BSHBinaryExpression node, CompilationUnit unit) {
        super();
        this.node = node;
        this.unit = unit;
    }

    /**
     * @param wrapper {@link TypeWrapper}
     * @return {@link TypeWrapper}
     */
    public static TypeWrapper unboxIfWrapper(TypeWrapper wrapper) {
        TypeWrapper unboxed = JavaClassWrapper.unboxType(wrapper);
        if (unboxed == null) {
            return wrapper;
        }
        return unboxed;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        if (this.node.kind == BSHBinaryExpression.INSTANCEOF) {
            compileInstanceOf(host, state, node.getChild(0), node.getChild(1));
        } else if (this.isBooleanShortcut()) {
            this.compileBooleanShortcut(host, state);
        } else {
            int original = state.getStackSize();
            TypeWrapper origLeft = unboxIfWrapper(this.unit.getNodeType(this.node.getChild(0), state.currentScope()));
            TypeWrapper origRight = unboxIfWrapper(this.unit.getNodeType(this.node.getChild(1), state.currentScope()));
            if (TypeWrapper.NULL_TYPE_WRAPPER == origLeft) {
                this.compileNullComparison(host, state, node.getChild(1));
            } else if (TypeWrapper.NULL_TYPE_WRAPPER == origRight) {
                this.compileNullComparison(host, state, node.getChild(0));
            } else if (JavaClassWrapper.STRING_WRAPPER.equals(origLeft) && this.node.kind == BSHBinaryExpression.PLUS) {
                this.compileStringAppend(host, state);
            } else {
                TypeWrapper targetType = promoteTypes(origLeft, origRight);
                this.compileBinaryOperation(host, state, targetType);
            }
            state.assertStackSize(original + 1);
        }
    }

    /**
     * compila expressoes do tipo 'instanceof'. O argumento typeToken deve ser do tipo {@link BSHType} e este deve estar visivel no
     * classloader
     * 
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param expression a expressao que vai gerar o elemento a ser comparado
     * @param typeToken {@link SimpleNode}
     * @throws CompilationFailedException e
     */
    private void compileInstanceOf(MethodVisitor host, CompilationState state, SimpleNode expression, SimpleNode typeToken)
            throws CompilationFailedException {
        int stack = state.getStackSize();
        this.unit.getNodeCompiler(expression).compile(host, state);
        state.assertStackSize(stack + 1);
        // ensure we box primitives
        this.unit.getCompilerManager().coerceStackTopTo(host, JavaClassWrapper.OBJECT_WRAPPER, state);
        TypeWrapper resolvedType = state.peekStack();
        TypeWrapper type = TypedVariableDeclarationCompiler.resolveTypeForNode((BSHType) typeToken, state.currentScope());
        if (type.equals(resolvedType)) {
            // statically verified that they are the same
            host.visitInsn(Opcodes.POP);
            state.popStack();
            BytecodeHelper.pushBooleanValue(true, host);
            state.pushStack(boolean.class);
        } else {
            // must emmit instanceof bytecode
            state.popStack();
            host.visitTypeInsn(Opcodes.INSTANCEOF, type.internalName());
            state.pushStack(boolean.class);
        }
        state.assertStackSize(stack + 1);
    }

    /**
     * Compila uma comparacao com null.
     * 
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param simpleNode o {@link SimpleNode} que contem a expressao. Se este for uma primitiva, ela sofrera 'boxing' e sera comparada com
     * null.
     * @throws CompilationFailedException e
     */
    private void compileNullComparison(MethodVisitor host, CompilationState state, SimpleNode simpleNode) throws CompilationFailedException {
        int stack = state.getStackSize();
        this.unit.getNodeCompiler(simpleNode).compile(host, state);
        if (state.peekStack().isPrimitive()) {
            // primitive and null. Java does not allow this, but we shortcircuit it to be != null
            int slots = state.peekStack().numberOfSlots();
            if (slots > 1) {
                host.visitInsn(Opcodes.POP2);
            } else {
                host.visitInsn(Opcodes.POP);
            }
            state.popStack();
            BytecodeHelper.pushBooleanValue(this.node.kind != BSHBinaryExpression.EQ, host);
            state.pushStack(boolean.class);
        } else if(state.peekStack() == TypeWrapper.NULL_TYPE_WRAPPER) {
            //statically known to be null so we shortcircuit
            host.visitInsn(Opcodes.POP);
            state.popStack();
            BytecodeHelper.pushBooleanValue(this.node.kind == BSHBinaryExpression.EQ, host);
            state.pushStack(boolean.class);
        } else {
            state.assertStackSize(stack + 1);
            boolean value = this.node.kind == BSHBinaryExpression.EQ;
            Label notNull = new Label();
            Label endIf = new Label();
            state.popStack();
            host.visitJumpInsn(Opcodes.IFNONNULL, notNull);
            BytecodeHelper.pushBooleanValue(value, host);
            host.visitJumpInsn(Opcodes.GOTO, endIf);
            host.visitLabel(notNull);
            BytecodeHelper.pushBooleanValue(!value, host);
            host.visitLabel(endIf);
            state.pushStack(boolean.class);
        }
        state.assertStackSize(stack + 1);
    }

    /**
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @throws CompilationFailedException e
     */
    private void compileStringAppend(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        int original = state.getStackSize();
        this.unit.compileNode(this.node.getChild(0), host, state);
        this.unit.compileNode(this.node.getChild(1), host, state);
        if (!state.peekStack().equals(JavaClassWrapper.STRING_WRAPPER)) {
            // call specialized version of String.valueOf
            MethodDescriptor method =
                    this.unit.reflectionUtils().findMethodForTypes(JavaClassWrapper.STRING_WRAPPER, "valueOf",
                    new TypeWrapper[]{ state.peekStack() });
            method.writeInvoke(host, state);
        }
        state.assertStackSize(original + 2);
        MethodDescriptor method = this.unit.reflectionUtils().getMethodWithName(String.class, "concat");
        method.writeInvoke(host, state);
        state.assertStackSize(original + 1);
        state.assertStackTopType(JavaClassWrapper.STRING_WRAPPER);
    }

    /**
     * Compila uma operacao binaria generica
     * 
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param targetType {@link TypeWrapper}
     * @throws CompilationFailedException e
     */
    private void compileBinaryOperation(MethodVisitor host, CompilationState state, TypeWrapper targetType)
            throws CompilationFailedException {
        if (JavaClassWrapper.OBJECT_WRAPPER.equals(targetType)) {
            this.compileObjectComparison(this.node.kind, state, host);
        } else {
            int original = state.getStackSize();
            this.unit.compileNode(this.node.getChild(0), host, state);
            TypeWrapper left = state.peekStack();
            // coerce to the target type, unboxing wrappers and widening
            // primitives
            this.unit.getCompilerManager().coerceToType(host, left, targetType, state);
            state.assertStackSize(original + 1);
            this.unit.compileNode(this.node.getChild(1), host, state);
            TypeWrapper right = state.peekStack();
            // coerce to the target type, unboxing wrappers and widening
            // primitives
            this.unit.getCompilerManager().coerceToType(host, right, targetType, state);
            state.assertStackSize(original + 2);
            if (JavaClassWrapper.BOOLEAN_WRAPPER.equals(targetType)) {
                this.compileBooleanExpression(this.node.kind, state, host);
            } else {
                this.compileNumericExpression(this.node.kind, state, host, targetType);
            }
        }
    }

    /**
     * Faz a compilacao de operadores genericos
     * 
     * @param kind o tipo da operacao
     * @param state o {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @throws CompilationFailedException e
     */
    private void compileObjectComparison(int kind, CompilationState state, MethodVisitor host) throws CompilationFailedException {
        int original = state.getStackSize();
        final SimpleNode left = this.node.getChild(0);
        final SimpleNode right = this.node.getChild(1);
        this.compileOperands(state, host, left, right);
        this.writeInvokeGenericOperation(kind, state, host, original);
    }

    /**
     * Compila a invocacao do metodo {@link CompiledScriptHelper#performBinaryOperation(Object, Object, int, int, Prototype)}
     * 
     * @param kind int tipo da operacao
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param original int
     * @throws CompilationFailedException e
     */
    private void writeInvokeGenericOperation(int kind, CompilationState state, MethodVisitor host, int original)
            throws CompilationFailedException {
        int id = this.unit.thisType().newDynamicInvocationId();
        BytecodeHelper.pushIntConst(id, host);
        state.pushStack(int.class);
        BytecodeHelper.pushIntConst(kind, host);
        state.pushStack(int.class);
        UnaryOperationNodeCompiler.pushThis(host, state);
        MethodDescriptor methodDescriptor = this.unit.reflectionUtils().getMethodWithName(CompiledScriptHelper.class, "performBinaryOperation");
        methodDescriptor.writeInvoke(host, state);
        state.assertStackSize(original + 1);
    }

    /**
     * Compila os operandos da operacao
     * 
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param left node
     * @param right node
     * @throws CompilationFailedException e
     */
    private void compileOperands(CompilationState state, MethodVisitor host, final SimpleNode left, final SimpleNode right)
            throws CompilationFailedException {
        int old = state.getStackSize();
        this.unit.compileNode(left, host, state);
        state.assertStackSize(old + 1);
        this.unit.getCompilerManager().coerceToType(host, state.peekStack(), JavaClassWrapper.OBJECT_WRAPPER, state);
        state.assertStackSize(old + 1);

        this.unit.compileNode(right, host, state);
        state.assertStackSize(old + 2);
        this.unit.getCompilerManager().coerceToType(host, state.peekStack(), JavaClassWrapper.OBJECT_WRAPPER, state);
        state.assertStackSize(old + 2);
    }

    /**
     * Compila uma operacao binaria envolvendo numeros
     * 
     * @param kind int
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @throws CompilationFailedException e
     */
    private void compileNumericExpression(int kind, CompilationState state, MethodVisitor host, TypeWrapper type)
            throws CompilationFailedException {
        if (JavaClassWrapper.BIG_DECIMAL_WRAPPER.equals(type)) {
            this.compileBigDecimalExpression(kind, state, host, type);
        } else if (JavaClassWrapper.FLOAT_WRAPPER.equals(type) || JavaClassWrapper.DOUBLE_WRAPPER.equals(type)) {
            this.compileFPNumberExpression(kind, state, host, type);
        } else {
            state.assertMinimumStackSize(2);
            state.popStack();
            state.popStack();
            if (this.compileArithmeticExpression(kind, type, host) || this.compileBitwiseExpression(kind, type, host)) {
                state.pushStack(type);
            } else if (this.compileComparisonExpression(kind, type, host)) {
                state.pushStack(boolean.class);
            } else {
                throw new CompilationFailedException(this.node.getLineNumber(), "unknown operator: " + ParserConstants.tokenImage[kind]);
            }
        }
    }

    /**
     * Compila uma operacao entre dois {@link BigDecimal}
     * 
     * @param kind int
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @throws CompilationFailedException e
     */
    private void compileBigDecimalExpression(int kind, CompilationState state, MethodVisitor host, TypeWrapper type)
            throws CompilationFailedException {
        // operands are on stacks
        if (this.compileBigDecimalComparison(kind, state, host, type)) {
            state.pushStack(boolean.class);
        } else if (this.compileBigDecimalArithmetic(kind, state, host, type)) {
            state.pushStack(BigDecimal.class);
        } else {
            // fallback to generic operation
            this.writeInvokeGenericOperation(kind, state, host, state.getStackSize() - 2);
        }
    }

    /**
     * Compila uma operacao aritmetica entre dois {@link BigDecimal}
     * 
     * @param kind int
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @return boolean se eh uma operacao aritmetica, false caso contrario
     * @throws CompilationFailedException e
     */
    private boolean compileBigDecimalArithmetic(int kind, CompilationState state, MethodVisitor host, TypeWrapper type)
            throws CompilationFailedException {

        switch (kind) {
        // arithmetic
        case ParserConstants.PLUS:
            // host.visitInsn(type.toASMType().getOpcode(Opcodes.IADD));
            UnaryOperationNodeCompiler.pushMathContext(state, host);
            state.popStack(NUM_3);
            // push math context
            host.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BigDecimal.class), "add", UnaryOperationNodeCompiler
                    .bigDecimalArithmeticMethod());
            break;

        case ParserConstants.MINUS:
            // host.visitInsn(type.toASMType().getOpcode(Opcodes.INEG));
            // host.visitInsn(type.toASMType().getOpcode(Opcodes.IADD));
            UnaryOperationNodeCompiler.pushMathContext(state, host);
            state.popStack(NUM_3);
            host.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BigDecimal.class), "subtract", UnaryOperationNodeCompiler
                    .bigDecimalArithmeticMethod());
            break;

        case ParserConstants.STAR:
            // host.visitInsn(type.toASMType().getOpcode(Opcodes.IMUL));
            UnaryOperationNodeCompiler.pushMathContext(state, host);
            state.popStack(NUM_3);
            host.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BigDecimal.class), "multiply", UnaryOperationNodeCompiler
                    .bigDecimalArithmeticMethod());
            break;

        case ParserConstants.SLASH:
            // host.visitInsn(type.toASMType().getOpcode(Opcodes.IMUL));
            UnaryOperationNodeCompiler.pushMathContext(state, host);
            state.popStack(NUM_3);
            host.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(BigDecimal.class), "divide", UnaryOperationNodeCompiler
                    .bigDecimalArithmeticMethod());
            break;
        default:
            return false;
        }
        return true;
    }

    /**
     * Compila uma operacao de comparacao entre dois {@link BigDecimal}
     * 
     * @param kind int
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @throws CompilationFailedException e
     * @return boolean
     */
    private boolean compileBigDecimalComparison(int kind, CompilationState state, MethodVisitor host, TypeWrapper type)
            throws CompilationFailedException {
        int opcode;
        switch (kind) {
        case ParserConstants.LT:
        case ParserConstants.LTX:
            opcode = Opcodes.IFLT;
            break;

        case ParserConstants.LE:
        case ParserConstants.LEX:
            opcode = Opcodes.IFLE;
            break;

        case ParserConstants.GT:
        case ParserConstants.GTX:
            opcode = Opcodes.IFGT;
            break;

        case ParserConstants.GE:
        case ParserConstants.GEX:
            opcode = Opcodes.IFGE;
            break;

        case ParserConstants.EQ:
            opcode = Opcodes.IFEQ;
            break;
        case ParserConstants.NE:
            opcode = Opcodes.IFNE;
            break;
        default:
            opcode = -1;
            break;
        }
        if (opcode == -1) {
            return false;
        }
        state.assertMinimumStackSize(2);
        MethodDescriptor withName = this.unit.reflectionUtils().getMethodWithName(Comparable.class, "compareTo");
        withName.writeInvoke(host, state);
        // must have an int on the stack by now
        state.assertStackTopType(JavaClassWrapper.INT_WRAPPER);
        state.popStack();
        Label finish = new Label(), push1 = new Label();
        host.visitJumpInsn(opcode, push1);
        BytecodeHelper.pushBooleanValue(false, host);
        host.visitJumpInsn(Opcodes.GOTO, finish);
        host.visitLabel(push1);
        BytecodeHelper.pushBooleanValue(true, host);
        host.visitLabel(finish);
        return true;
    }

    /**
     * Compila uma operacao de comparacao entre duas primitivas
     * 
     * @param kind int
     * @param type {@link TypeWrapper}
     * @param host {@link MethodVisitor}
     * @return boolean
     */
    private boolean compileComparisonExpression(int kind, TypeWrapper type, MethodVisitor host) {
        if (!this.unit.reflectionUtils().isIntPrimitive(type)) {
            int convertionOpcode;
            if (JavaClassWrapper.LONG_WRAPPER.equals(type)) {
                convertionOpcode = Opcodes.LCMP;
            } else if (JavaClassWrapper.FLOAT_WRAPPER.equals(type)) {
                convertionOpcode = Opcodes.FCMPG; // ?
            } else {
                convertionOpcode = Opcodes.DCMPG; // ?
            }
            host.visitInsn(convertionOpcode);
            return this.compileCompareWithZero(kind, type, host);
        } else {
            return this.compileIntegralComparison(kind, host);
        }
    }

    /**
     * Compila uma comparacao com zero
     * 
     * @param kind int
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @return boolean
     */
    private boolean compileCompareWithZero(int kind, TypeWrapper type, MethodVisitor host) {
        int opcode = -1;
        switch (kind) {
        case ParserConstants.LT:
        case ParserConstants.LTX:
            opcode = Opcodes.IFLT;
            break;

        case ParserConstants.LE:
        case ParserConstants.LEX:
            opcode = Opcodes.IFLE;
            break;

        case ParserConstants.GT:
        case ParserConstants.GTX:
            opcode = Opcodes.IFGT;
            break;

        case ParserConstants.GE:
        case ParserConstants.GEX:
            opcode = Opcodes.IFGE;
            break;

        case ParserConstants.EQ:
            opcode = Opcodes.IFEQ;
            break;
        case ParserConstants.NE:
            opcode = Opcodes.IFNE;
            break;
        default:
            opcode = -1;
            break;
        }
        if (opcode == -1) {
            return false;
        }
        Label finish = new Label(), push1 = new Label();
        host.visitJumpInsn(opcode, push1);
        BytecodeHelper.pushBooleanValue(false, host);
        host.visitJumpInsn(Opcodes.GOTO, finish);
        host.visitLabel(push1);
        BytecodeHelper.pushBooleanValue(true, host);
        host.visitLabel(finish);
        return true;
    }

    /**
     * Compila uma comparacao inteira
     * 
     * @param kind int
     * @param host {@link MethodVisitor}
     * @return boolean
     */
    private boolean compileIntegralComparison(int kind, MethodVisitor host) {
        int opcode = -1;
        switch (kind) {
        case ParserConstants.LT:
        case ParserConstants.LTX:
            opcode = Opcodes.IF_ICMPLT;
            break;

        case ParserConstants.LE:
        case ParserConstants.LEX:
            opcode = Opcodes.IF_ICMPLE;
            break;

        case ParserConstants.GT:
        case ParserConstants.GTX:
            opcode = Opcodes.IF_ICMPGT;
            break;

        case ParserConstants.GE:
        case ParserConstants.GEX:
            opcode = Opcodes.IF_ICMPGE;
            break;

        case ParserConstants.EQ:
            opcode = Opcodes.IF_ICMPEQ;
            break;
        case ParserConstants.NE:
            opcode = Opcodes.IF_ICMPNE;
            break;
        default:
            opcode = -1;
            break;
        }
        if (opcode == -1) {
            return false;
        }
        Label finish = new Label(), push1 = new Label();
        host.visitJumpInsn(opcode, push1);
        BytecodeHelper.pushBooleanValue(false, host);
        host.visitJumpInsn(Opcodes.GOTO, finish);
        host.visitLabel(push1);
        BytecodeHelper.pushBooleanValue(true, host);
        host.visitLabel(finish);
        return true;
    }

    /**
     * Compila uma operacao bit a bit
     * 
     * @param kind int
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @return boolean
     */
    private boolean compileBitwiseExpression(int kind, TypeWrapper type, MethodVisitor host) {
        switch (kind) {
        // bitwise
        case ParserConstants.LSHIFT:
        case ParserConstants.LSHIFTX:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.ISHL));
            break;

        case ParserConstants.RSIGNEDSHIFT:
        case ParserConstants.RSIGNEDSHIFTX:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.ISHR));
            break;

        case ParserConstants.RUNSIGNEDSHIFT:
        case ParserConstants.RUNSIGNEDSHIFTX:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IUSHR));
            break;

        case ParserConstants.BIT_AND:
        case ParserConstants.BIT_ANDX:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IAND));
            break;
        case ParserConstants.BIT_OR:
        case ParserConstants.BIT_ORX:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IOR));
            break;

        case ParserConstants.XOR:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IXOR));
            break;

        default:
            return false;
        }
        return true;

    }

    /**
     * Compila uma operacao aritmetica
     * 
     * @param kind int
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @return boolean
     */
    private boolean compileArithmeticExpression(int kind, TypeWrapper type, MethodVisitor host) {
        switch (kind) {
        // arithmetic
        case ParserConstants.PLUS:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IADD));
            break;

        case ParserConstants.MINUS:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.INEG));
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IADD));
            break;

        case ParserConstants.STAR:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IMUL));
            break;
        case ParserConstants.SLASH:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IDIV));
            break;
        case ParserConstants.MOD:
            host.visitInsn(type.toASMType().getOpcode(Opcodes.IREM));
            break;
        default:
            return false;
        }
        return true;
    }

    /**
     * Compila uma expressao entre numeros de ponto flutuante
     * 
     * @param kind int
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @param type {@link TypeWrapper}
     * @throws CompilationFailedException e
     */
    private void compileFPNumberExpression(int kind, CompilationState state, MethodVisitor host, TypeWrapper type)
            throws CompilationFailedException {
        state.assertMinimumStackSize(2);
        state.popStack();
        state.popStack();
        if (this.compileArithmeticExpression(kind, type, host)) {
            state.pushStack(type);
        } else if (this.compileComparisonExpression(kind, type, host)) {
            state.pushStack(boolean.class);
        } else {
            throw new CompilationFailedException(this.node.getLineNumber(), "unknown operator: " + ParserConstants.tokenImage[kind]);
        }
    }

    /**
     * Compila uma operacao entre booleans
     * 
     * @param kind int
     * @param state {@link CompilationState}
     * @param host {@link MethodVisitor}
     * @throws CompilationFailedException e
     */
    private void compileBooleanExpression(int kind, CompilationState state, MethodVisitor host) throws CompilationFailedException {
        state.assertMinimumStackSize(2);
        state.popStack();
        state.popStack();
        switch (kind) {
        case BSHBinaryExpression.BIT_AND:
        case BSHBinaryExpression.BIT_ANDX:
            host.visitInsn(Opcodes.IAND);
            break;

        case BSHBinaryExpression.BIT_OR:
        case BSHBinaryExpression.BIT_ORX:
            host.visitInsn(Opcodes.IOR);
            break;

        case BSHBinaryExpression.EQ:
        case BSHBinaryExpression.NE:
            this.compileIntegralComparison(kind, host);
            break;

        default:
            throw new CompilationFailedException(this.node.getLineNumber(), "unknown operand for boolean type: "
                    + ParserConstants.tokenImage[kind]);
        }
        state.pushStack(boolean.class);
    }

    /**
     * Promove tipos
     * 
     * @param leftSide {@link TypeWrapper}
     * @param rightSide {@link TypeWrapper}
     * @return {@link TypeWrapper}
     * @throws CompilationFailedException e
     */
    public static TypeWrapper promoteTypes(TypeWrapper leftSide, TypeWrapper rightSide) throws CompilationFailedException {
        if (leftSide.equals(JavaClassWrapper.BOOLEAN_WRAPPER)) {
            if (rightSide.equals(JavaClassWrapper.BOOLEAN_WRAPPER)) {
                return JavaClassWrapper.BOOLEAN_WRAPPER;
            }
            // throw new CompilationFailedException(line,
            // "required boolean but was: " + rightSide);
            return JavaClassWrapper.OBJECT_WRAPPER;
        } else if (rightSide.equals(JavaClassWrapper.BOOLEAN_WRAPPER)) {
            // throw new CompilationFailedException(line,
            // "required boolean but was: " + leftSide);
            return JavaClassWrapper.OBJECT_WRAPPER;
        }
        TypeWrapper widest = JavaClassWrapper.largestNumberType(leftSide, rightSide);
        if (widest == null) {
            return JavaClassWrapper.OBJECT_WRAPPER;
        }
        return widest;
    }

    /**
     * Compila um atalho de expressoes booleanas && e ||
     * 
     * @param visitor {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @throws CompilationFailedException e
     */
    private void compileBooleanShortcut(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
        Label cont = new Label();
        if (this.node.kind == BSHBinaryExpression.BOOL_AND || this.node.kind == BSHBinaryExpression.BOOL_ANDX) {
            // 1st node
            this.unit.compileNode(this.node.getChild(0), visitor, state);
            // UnaryOperationNodeCompiler.checkStackTopUnboxWrappers(visitor,
            // state, node.getLineNumber(), unit);
            state.getUnit().getCompilerManager().coerceStackTopTo(visitor, JavaClassWrapper.BOOLEAN_WRAPPER, state);
            state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
            Label push0 = new Label();
            state.popStack();
            // if false, jump to push0
            visitor.visitJumpInsn(Opcodes.IFEQ, push0);

            // 2nd node
            this.unit.compileNode(this.node.getChild(1), visitor, state);
            // UnaryOperationNodeCompiler.checkStackTopUnboxWrappers(visitor,
            // state, node.getLineNumber(), unit);
            state.getUnit().getCompilerManager().coerceStackTopTo(visitor, JavaClassWrapper.BOOLEAN_WRAPPER, state);
            state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
            state.popStack();
            // if false, jump to push0
            visitor.visitJumpInsn(Opcodes.IFEQ, push0);
            // else, push 'true' and GOTO cont
            BytecodeHelper.pushBooleanValue(true, visitor);
            visitor.visitJumpInsn(Opcodes.GOTO, cont);
            visitor.visitLabel(push0);
            BytecodeHelper.pushBooleanValue(false, visitor);
            // push boolean
            state.pushStack(boolean.class);
        } else {
            Label push1 = new Label();
            this.unit.compileNode(this.node.getChild(0), visitor, state);
            // UnaryOperationNodeCompiler.checkStackTopUnboxWrappers(visitor,
            // state, node.getLineNumber(), unit);
            state.getUnit().getCompilerManager().coerceStackTopTo(visitor, JavaClassWrapper.BOOLEAN_WRAPPER, state);
            state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
            state.popStack();
            // if true, jmp to push true
            visitor.visitJumpInsn(Opcodes.IFNE, push1);
            this.unit.compileNode(this.node.getChild(1), visitor, state);
            // UnaryOperationNodeCompiler.checkStackTopUnboxWrappers(visitor,
            // state, node.getLineNumber(), unit);
            state.getUnit().getCompilerManager().coerceStackTopTo(visitor, JavaClassWrapper.BOOLEAN_WRAPPER, state);
            state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
            state.popStack();
            visitor.visitJumpInsn(Opcodes.IFNE, push1);
            // push false and jump to continue
            BytecodeHelper.pushBooleanValue(false, visitor);
            visitor.visitJumpInsn(Opcodes.GOTO, cont);
            // push true and continue
            visitor.visitLabel(push1);
            BytecodeHelper.pushBooleanValue(true, visitor);
            state.pushStack(boolean.class);
        }
        visitor.visitLabel(cont);
    }

    /**
     * {@inheritDoc}
     * 
     * @see bsh.NodeCompiler#nodeResultType(bsh.meta.Scope)
     */
    public TypeWrapper nodeResultType(Scope scope) throws CompilationFailedException {
        if (this.isBooleanShortcut()) {
            return JavaClassWrapper.BOOLEAN_WRAPPER;
        }
        final TypeWrapper rawType = this.unit.getNodeType(this.node.getChild(0), scope);
        if (JavaClassWrapper.wrapperFor(String.class).equals(rawType) && this.node.kind == BSHBinaryExpression.PLUS) {
            return rawType;
        }
        final TypeWrapper leftType = JavaClassWrapper.unboxType(rawType);
        if (leftType == null) {
            return JavaClassWrapper.wrapperFor(Object.class);
        }
        final TypeWrapper rawType1 = this.unit.getNodeType(this.node.getChild(1), scope);
        final TypeWrapper rightType = JavaClassWrapper.unboxType(rawType1);
        if (rightType == null) {
            return JavaClassWrapper.OBJECT_WRAPPER;
        }
        return promoteTypes(leftType, rightType);
    }

    /**
     * @return boolean
     */
    private boolean isBooleanShortcut() {
        return this.node.kind == BSHBinaryExpression.BOOL_ANDX || this.node.kind == BSHBinaryExpression.BOOL_AND
                || this.node.kind == BSHBinaryExpression.BOOL_OR
                || this.node.kind == BSHBinaryExpression.BOOL_ORX;
    }

}
